package cn.sccl.common.scaffold;

import freemarker.template.Configuration;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.*;

/**
 * 脚手架生成器
 * Created by 杨浩宇 on 2016-02-20.
 */
public class ScaffoldGenerator {
    private static final String JDBC_PASSWORD = "jdbc.password";
    private static final String JDBC_USER = "jdbc.user";
    private static final String JDBC_URL = "jdbc.url";
    private static final String JDBC_DRIVER = "jdbc.driver";
    private static final String JDBC_SCHEMA = "jdbc.schema";
    private static final String CONFIG_PROPERTIES = "dirms-config.properties";
    private static final String COLUMN_NAME = "COLUMN_NAME";
    private static final String TYPE_NAME = "TYPE_NAME";
    private static final String COLUMN_SIZE = "COLUMN_SIZE";
    private static final String NULLABLE = "NULLABLE";
    private static final String DECIMAL_DIGITS = "DECIMAL_DIGITS";

    private static final String DEFAULT_PRIMARY_KEY = "ID";
    private static final String MYSQL_FLAG = "mysql";

    private static final Map<String, String> propertisInBaseModel = new HashMap<String, String>();

    private Log log = LogFactory.getLog(this.getClass());

//    private String projectPath;//项目路径

    private String projectJavaPath;

    private String projectResourcePath;

    private String orgBasePackage;//项目的基础包

    private List<FileGenerator> fileGenerators;//所有文件生成处理器

    private Configuration configuration;//freemaker的配置

    private Connection connection;//数据库连接

    private DatabaseMetaData metaData;//数据库元信息

    private String schema;//数据库schema

    private String databaseType;


    /**
     * @param javaPath        java文件路径，前后不要带“/”
     * @param resourcePath    资源文件路径，前后不要带“/”
     * @param outputClassPath 输出的class文件路径
     * @param orgBasePackage  项目基本包
     * @throws IOException
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public ScaffoldGenerator(String javaPath, String resourcePath, String outputClassPath, String orgBasePackage, String databaseType) {
        this.databaseType = databaseType;
        if (this.databaseType == null || this.databaseType.isEmpty()) {
            this.databaseType = MYSQL_FLAG;
        }
        this.orgBasePackage = orgBasePackage;
        fileGenerators = new ArrayList<FileGenerator>();

        //初始化项目路径
        String path = this.getClass().getResource("/").getPath();
        path = path.replace(outputClassPath, "");
        path = path.substring(0, path.length() - 1);
        this.projectJavaPath = path + javaPath;
        this.projectResourcePath = path + resourcePath;


        //以下字段处于BaseModel中，不需要在具体类中生成
        propertisInBaseModel.put("ID", "ID");
        propertisInBaseModel.put("CREATE_TIME", "CREATE_TIME");
        propertisInBaseModel.put("UPDATE_TIME", "UPDATE_TIME");
        propertisInBaseModel.put("DELETE_FLAG", "DELETE_FLAG");
        propertisInBaseModel.put("POSITION_CODE", "POSITION_CODE");
        propertisInBaseModel.put("YEAR", "YEAR");
        propertisInBaseModel.put("PROVINCE_CODE", "PROVINCE_CODE");


        //初始化freemaker
        configuration = new Configuration();
        try {
            configuration.setDirectoryForTemplateLoading(new File(getClass().getResource("/").getPath() + "/templates"));//模板文件所在目录
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成脚手架需要的文件
     *
     * @param basePackage 基础包
     * @param modelName   模型名称
     * @param tableName   数据库表
     */
    public void execute(String basePackage, String modelName, String tableName) {
        System.out.println("========================开始为：" + modelName + "生成脚手架文件===================");
        try {
            //初始化数据库
            initConnection();

            FileConfig fileConfig = new FileConfig(orgBasePackage, basePackage);
            fileConfig.setModelName(modelName);

            //得到表信息
//            TableInfo tableInfo = getTableInfo(tableName.toUpperCase());
            TableInfo tableInfo = getTableInfo(tableName);
            fileConfig.setTableInfo(tableInfo);

            //逐个调用文件生成器，生成脚手架需要的所有文件
            for (FileGenerator fileGenerator : fileGenerators) {
                String projectPath = fileGenerator.storeToJavaSrc() ? projectJavaPath : projectResourcePath;//判断文件需要存储的位置
                String filePath = fileGenerator.getTargetPath(projectPath, fileConfig.getBasePackagePath(), modelName);
                if (!fileGenerator.storeToJavaSrc()) {
                    filePath = filePath.substring(0,filePath.length()-".xml".length()) + "-" + this.databaseType + ".xml";
                }
                File targetFile = new File(filePath);//最终需要输出的文件
                if (!targetFile.exists()) {//无文件时，创建文件
                    targetFile.getParentFile().mkdirs();
                    targetFile.createNewFile();
                }
                fileConfig.setTargetFile(targetFile);

                fileGenerator.generator(configuration, fileConfig);//生成文件
                System.out.println("成功生成文件：" + targetFile.getPath());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeAll(connection, null, null);
        }
    }

    private boolean initConnection() throws IOException, ClassNotFoundException, SQLException {
        Properties properties = new Properties();
        properties.load(new FileInputStream(new File(getClass().getResource("/").getPath() + "/" + CONFIG_PROPERTIES)));

        String driverClass = properties.getProperty(JDBC_DRIVER);
        String url = properties.getProperty(JDBC_URL);
        String user = properties.getProperty(JDBC_USER);
        String password = properties.getProperty(JDBC_PASSWORD);
        schema = properties.getProperty(JDBC_SCHEMA);
        if (StringUtils.isBlank(schema)) {
            schema = user.toUpperCase();
        }

        try {
            Class.forName(driverClass);
        } catch (ClassNotFoundException e) {
            System.out.println("未定义数据库驱动，或定义的驱动【" + driverClass + "】无法加载！");
            throw e;
        }

        try {
            connection = DriverManager.getConnection(url, user, password);
            metaData = connection.getMetaData();
        } catch (SQLException e) {
            System.out.println("初始化数据库连接时错误，请检查连接的url、用户名、密码等信息是否正确！");
            if (connection != null && !connection.isClosed()) {
                connection.close();
            }
            throw e;
        }

        return true;
    }

    private TableInfo getTableInfo(String tableName) throws SQLException {
        TableInfo tableInfo = new TableInfo();
        tableInfo.setTableName(tableName);

        //获取表的注释信息
        fillTableComments(tableInfo);

        //获取主键
        fillPrimaryKey(tableInfo);

        //获取列信息
        fillColumns(tableInfo);

        return tableInfo;

    }

    /**
     * 获取表的注释信息
     *
     * @param tableInfo
     */
    private void fillTableComments(TableInfo tableInfo) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            if (this.databaseType.equals(MYSQL_FLAG)) {
                String tabSql = "select t.table_name, t.table_comment from information_schema.tables t where t.table_name = lower('" + tableInfo.getTableName() + "')";
                ps = connection.prepareStatement(tabSql);
                rs = ps.executeQuery();
                if (rs.next()) {
                    tableInfo.setTableComments(rs.getString("TABLE_COMMENT"));
                }
            } else {
                String tabSql = "select t.table_name , t.comments from user_tab_comments t where t.table_name = upper('" + tableInfo.getTableName() + "')";
                ps = connection.prepareStatement(tabSql);
                rs = ps.executeQuery();
                if (rs.next()) {
                    tableInfo.setTableComments(rs.getString("COMMENTS"));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("获取表的注释信息失败！", e.getCause());
        } finally {
            closeAll(null, ps, rs);
        }

    }

    /**
     * 获取表的主键信息
     *
     * @param tableInfo
     */
    private void fillPrimaryKey(TableInfo tableInfo) {
        ResultSet rs = null;
        String errorMsg = null;
        try {
            rs = metaData.getPrimaryKeys(null, schema, tableInfo.getTableName());
            if (rs.next()) {
                tableInfo.setPrimaryKey(rs.getString(COLUMN_NAME));
                if (!DEFAULT_PRIMARY_KEY.equals(tableInfo.getPrimaryKey().toUpperCase())) {
                    errorMsg = "表 " + tableInfo.getTableName() + " 设计的主键列名称不是【" + DEFAULT_PRIMARY_KEY + "】，无法利用脚手架！";
                    throw new RuntimeException(errorMsg);
                }
            } else {
                rs = metaData.getColumns(null,schema,tableInfo.getTableName(),DEFAULT_PRIMARY_KEY);
                if (rs.next()) {
                    tableInfo.setPrimaryKey(rs.getString(COLUMN_NAME));
                }else{
                    errorMsg = "表 " + tableInfo.getTableName() + " 未设计主键，无法利用脚手架！";
                    throw new RuntimeException(errorMsg);
                }
            }

            if (rs.next()) {
                errorMsg = "表 " + tableInfo.getTableName() + " 存在复合主键，无法利用脚手架！";
                throw new RuntimeException(errorMsg);
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("获取主键信息失败！", e.getCause());
        } finally {
            closeAll(null, null, rs);
        }
    }

    private void fillColumns(TableInfo tableInfo) {
        PreparedStatement ps = null;
        ResultSet rs = null;

        //获取表列的注释comments
        Map<String, String> columnExplains = new HashMap<String, String>();
        try {
            if (this.databaseType.equals(MYSQL_FLAG)) {
                String sql = "select t.column_name, t.column_comment from information_schema.columns t where t.table_name=lower('" + tableInfo.getTableName() + "')";
                ps = connection.prepareStatement(sql);
                rs = ps.executeQuery();
                while (rs.next()) {
                    columnExplains.put(rs.getString("COLUMN_NAME"), rs.getString("COLUMN_COMMENT"));
                }
            } else {
                String sql = " SELECT T.TABLE_NAME,T.COLUMN_NAME,T.COMMENTS FROM USER_COL_COMMENTS  T WHERE T.TABLE_NAME=upper('" + tableInfo.getTableName() + "')  ";
                ps = connection.prepareStatement(sql);
                rs = ps.executeQuery();
                while (rs.next()) {
                    columnExplains.put(rs.getString("COLUMN_NAME"), rs.getString("COMMENTS"));
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException("获取列的注释信息失败！", ex.getCause());
        } finally {
            closeAll(null, ps, rs);
        }

        //获取列信息
        try {
            rs = this.metaData.getColumns(connection.getCatalog(), schema, tableInfo.getTableName(), null);
            while (rs.next()) {
                ColumnInfo columnInfo = new ColumnInfo();
                columnInfo.setColumnName(rs.getString(COLUMN_NAME));
                columnInfo.setOrgJdbcType(rs.getString(TYPE_NAME));
                columnInfo.setSize(rs.getInt(COLUMN_SIZE));
                columnInfo.setNullable(rs.getBoolean(NULLABLE));
                columnInfo.setDigits(rs.getInt(DECIMAL_DIGITS));
                columnInfo.setComments(columnExplains.get(columnInfo.getColumnName()));
                columnInfo.setInBaseModel(propertisInBaseModel.containsKey(columnInfo.getColumnName().toUpperCase()));
                tableInfo.getColumnInfos().add(columnInfo);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException("获取列信息失败！", ex.getCause());
        } finally {
            closeAll(connection, ps, rs);
        }
    }

    private void closeAll(Connection connection, PreparedStatement preparedStatement, ResultSet rs) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 注册需要的文件生成器
     *
     * @param fileGenerator
     */
    public void registFileGenerator(FileGenerator fileGenerator) {
        fileGenerators.add(fileGenerator);
    }

    public String getDatabaseType() {
        return databaseType;
    }

    public void setDatabaseType(String databaseType) {
        this.databaseType = databaseType;
    }
}
